/*
 * $Id: FTPCommand.java,v 1.15 2012/01/30 15:52:15 navdeep_mahajan Exp $
 * 
 * Copyright (C) 2006 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.common.commands;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPReply;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.exceptions.FTPCommandException;
import com.ge.healthcare.autosc.common.exceptions.InvalidLoginCredentialsException;
import com.ge.healthcare.autosc.common.util.ASCFileHandler;
import com.ge.healthcare.autosc.common.util.ApplicationInfo;
import com.ge.healthcare.autosc.common.util.PropertyConstants;
import com.ge.med.service.net.parser.RSTFTPFileEntryParserFactory;

/**
 * Purpose:
 * <ul>
 * <li>Provides methods to run FTP Commands on ftp server</li>
 * </ul>
 * 
 * @author Navdeep Mahajan
 */
public class FTPCommand implements IFTCommand {
	private static final String PASSIVE = "PASSIVE";

	private FTPClient ftp = null;
	private int iDefaultTimeout = 0;
	private int iDataTimeout = 0;
	private int iSocketTimeout = 0;
	public static final int BINARY_FILE_TRANSFER = FTP.BINARY_FILE_TYPE;
	public static final int ASCII_FILE_TRANSFER = FTP.ASCII_FILE_TYPE;

	/**
	 * Initializes isdFtFtpSftpDetails and the ftp client
	 * 
	 * @param isdFtFtpSftpDetails
	 */
	public FTPCommand() {
		ftp = new FTPClient();
		ftp.setParserFactory(new RSTFTPFileEntryParserFactory());
		if (PASSIVE.equalsIgnoreCase(ApplicationInfo.getInstance()
				.getProperties().getProperty(
						PropertyConstants.FILE_TRANSFER_MODE, PASSIVE))) {
			ftp.enterLocalPassiveMode();
		} else {
			ftp.enterLocalActiveMode();
		}
		iDefaultTimeout = Integer.parseInt(ApplicationInfo.getInstance()
				.getProperties().getProperty(PropertyConstants.FILE_TRANSFER_DEFAULT_TIMEOUT, "60000"));
		iDataTimeout = Integer.parseInt(ApplicationInfo.getInstance()
				.getProperties().getProperty(PropertyConstants.FILE_TRANSFER_DATA_TIMEOUT, "60000"));
		iSocketTimeout = Integer.parseInt(ApplicationInfo.getInstance()
				.getProperties().getProperty(PropertyConstants.FILE_TRANSFER_SOCKET_TIMEOUT, "60000"));
		ftp.setDefaultTimeout(iDefaultTimeout);
	}

	/**
	 * Method to connect to the FTP Server
	 * 
	 * @throws FTPCommandException
	 */
	public void connect(String host) throws FTPCommandException {
		try {
			int reply;
			ftp.connect(host);
			// After connection attempt, you should check the reply code to
			// verify success.
			reply = ftp.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftp.disconnect();
				throw new FTPCommandException(new StringBuffer("Reply Code : ")
						.append(ftp.getReplyCode()).append(" Reply String : ")
						.append(ftp.getReplyString()).toString());
			}
			try {
				ASCLogger.info(this.getClass(), "connect", "Setting ftp timeouts to:" + iSocketTimeout);
				ftp.setSoTimeout(iSocketTimeout);
				ftp.setDataTimeout(iDataTimeout);
			} catch (SocketException dce) {
				ftp.disconnect();
				throw new FTPCommandException("Failed setting socket timeout", dce);
			}
		} catch (IOException ioExcep) {
			if (ftp.isConnected()) {
				try {
					ftp.disconnect();
				} catch (IOException ioException) {
					// Ignore if we can't disconnect
					throw new FTPCommandException(ioExcep);
				}
			}
			throw new FTPCommandException(ioExcep);
		}
	}

	/**
	 * Method for logging in to the remote server
	 * 
	 * @return boolean - indicating success or failure of login
	 * @throws FTPConnectionClosedException
	 * @throws IOException
	 * @throws FTPCommandException
	 * @throws InvalidLoginCredentialsException
	 */
	public void login(String host, String userName, String passcode)
			throws FTPCommandException, InvalidLoginCredentialsException {
		this.connect(host);
		try {
			if (!(ftp.login(userName, passcode))) {
				ftp.logout();
				throw new InvalidLoginCredentialsException(
						"Login to the FTP Server failed");
			} else {
				ftp.setFileType(FTP.BINARY_FILE_TYPE);
			}
		} catch (IOException ioException) {
			throw new FTPCommandException(ioException);
		}
	}

	/**
	 * Sets the transfer mode
	 * 
	 * @param mode
	 * @throws FTPCommandException
	 */
	public void setTransferMode(int mode) throws FTPCommandException {
		try {
			ftp.setFileType(mode);
		} catch (IOException ioException) {
			throw new FTPCommandException(ioException);
		}
	}

	/**
	 * Sets file transfer mode as binary
	 * 
	 * @throws FTPCommandException
	 */
	public void setBinaryTransferMode() throws FTPCommandException {
		this.setTransferMode(BINARY_FILE_TRANSFER);
	}

	/**
	 * Sets ascii transfer mode
	 * 
	 * @throws FTPCommandException
	 */
	public void setAsciiTransferMode() throws FTPCommandException {
		this.setTransferMode(ASCII_FILE_TRANSFER);
	}

	/**
	 * Method changes the working directory on the FTP Server
	 * 
	 * @param dir
	 * @return
	 * @throws FTPCommandException
	 */
	public boolean cd(String dir) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(dir)) {
			throw new FTPCommandException(new StringBuffer().append("dir : ")
					.append(dir).append(
							" of the cd method of FTPCommand can't be Null.")
					.toString());
		}
		try {
			ftp.changeWorkingDirectory(dir);
			if (!this.isPositiveFTPReply(ftp.getReplyCode())) {
				return false;
			}
		} catch (IOException ioExcep) {
			throw new FTPCommandException(ioExcep);
		}
		return true;
	}

	/**
	 * @see com.ge.healthcare.service.mdex.commands.IFTCommand#cd(java.lang.String,
	 *      boolean)
	 */
	public boolean cd(String dir, boolean throwExcep)
			throws FTPCommandException {
		if (!this.cd(dir)) {
			if (throwExcep) {
				throw new FTPCommandException(new StringBuffer().append(
						"The Required dir [").append(dir)
						.append("] is missing").toString());
			}
		}
		return true;
	}

	/**
	 * Disconnect the FTP Connect
	 */
	public void logout() {
		try {
			if (! ASCUtil.isNullOrEmptyObject(ftp) && ftp.isConnected()){
				ftp.logout();
				if (ftp.isConnected()) {
					ftp.disconnect();
				}
			}
		} catch (IOException ioExcep) {
			// do nothing. No point throwing something
			return;
		}
	}

	/**
	 * Used to place the file to the remote location
	 * 
	 * @param local
	 * @param remote
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws FTPCommandException
	 */
	public void putFile(String local, String remote) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(local)) {
			throw new FTPCommandException(new StringBuffer().append(
					"localFile : ").append(local).append(
					" of the putFile method of FTPCommand can't be Null.")
					.toString());
		} else if (ASCUtil.isNullOrEmpty(remote)) {
			throw new FTPCommandException(new StringBuffer().append(
					"remoteFile : ").append(remote).append(
					" of the putFile method of FTPCommand can't be Null.")
					.toString());
		}
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(local);
			ftp.storeFile(remote, fis);
			if (!this.isPositiveFTPReply(ftp.getReplyCode())) {
				throw new FTPCommandException(new StringBuffer().append(
						"Got reply code : ").append(
						ftp.getReplyCode() + " reply string : ").append(
						ftp.getReplyString()).toString());
			}
		} catch (FileNotFoundException fileNotFound) {
			throw new FTPCommandException(fileNotFound);
		} catch (IOException ioExcep) {
			throw new FTPCommandException(ioExcep);
		} finally {
			try {
				if (fis != null) {
					fis.close();
				}
			} catch (IOException ioExcep) {
				// Ignore in case we failed to close the input stream
				fis = null;
			}
		}
	}

	/**
	 * Method used to retrieve the remote file
	 * 
	 * @param remoteFile
	 * @param localFile
	 * @throws FTPCommandException
	 */
	public void getFile(String remoteFile, String localFile)
			throws FTPCommandException {
		FileOutputStream outputStream = null;
		try {
			outputStream = new FileOutputStream(new File(localFile));
			ftp.retrieveFile(remoteFile, outputStream);
			outputStream.flush();
		} catch (IOException ioException) {
			throw new FTPCommandException(new StringBuffer().append(
					"Failed to retrieve remoteFile:").append(remoteFile)
					.append(" as localFile:").append(localFile).toString(),
					ioException);
		} finally {
			ASCFileHandler.closeOutputStream(outputStream);
		}
	}

	/**
	 * Deletes the remote file
	 * @param remoteFile
	 * @param suppressDeleted suppress the exception if delete is not successful,
	 * which could mean file has already been deleted.
	 * @throws FTPCommandException
	 */
	public void deleteFile(String remoteFile, boolean suppressDeleted) throws FTPCommandException {
		try {
			boolean deleted = ftp.deleteFile(remoteFile);
			if ((!deleted) && (!suppressDeleted)) {
				throw new FTPCommandException(new StringBuffer().append(
						"Failed to delete remoteFile:").append(remoteFile)
						.toString());
			}
		} catch (IOException ioException) {
			throw new FTPCommandException(new StringBuffer().append(
					"Failed to delete remoteFile:").append(remoteFile)
					.toString(), ioException);
		}
	}

	/**
	 * Check to see if the reply code returned is a positive completion code
	 * 
	 * @param reply
	 * @return
	 */
	private boolean isPositiveFTPReply(int reply) {
		return FTPReply.isPositiveCompletion(reply)
				|| FTPReply.isPositiveIntermediate(reply)
				|| FTPReply.isPositivePreliminary(reply);
	}

	/**
	 * Used to make directories on the remote server. The method also changes
	 * the permissions on the remote server to 777
	 * 
	 * @param pathname
	 * @throws FTPCommandException
	 */
	public void mkdir(String pathname) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(pathname)) {
			throw new FTPCommandException(new StringBuffer().append(
					"pathname : ").append(pathname).append(
					" of the mkdir method of FTPCommand can't be Null.")
					.toString());
		}
		try {
			boolean dirCreated = ftp.makeDirectory(pathname);
			if (!dirCreated) {
				throw new FTPCommandException(
						new StringBuffer()
								.append(
										"Failed to create directory using FTP. Permission Denied. Path: ")
								.append(pathname).toString());
			}
		} catch (IOException ioExcep) {
			throw new FTPCommandException(
					new StringBuffer()
							.append(
									"Failed to create directory using FTP. Permission Denied. Path: ")
							.append(pathname).append(" Received Message : ")
							.append(ioExcep.getMessage()).toString(), ioExcep);
		}
	}

	/**
	 * Removes the specified directory from the server
	 * 
	 * @param pathname
	 * @throws FTPCommandException
	 */
	public void rmdir(String pathname) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(pathname)) {
			throw new FTPCommandException(new StringBuffer().append(
					"pathname : ").append(pathname).append(
					" of the rmdir method of FTPCommand can't be Null.")
					.toString());
		}
		try {
			boolean dirDeleted = ftp.removeDirectory(pathname);
			if (!dirDeleted) {
				throw new FTPCommandException(new StringBuffer().append(
						"Failed to delete directory using FTP. Path: ").append(
						pathname).toString());
			}
		} catch (IOException ioExcep) {
			throw new FTPCommandException(new StringBuffer().append(
					"Failed to remove directory using FTP. Path: ").append(
					pathname).append(" Received Message : ").append(
					ioExcep.getMessage()).toString(), ioExcep);
		}
	}

	/**
	 * Method to change permissions on file or directory
	 * 
	 * @param ownerPermission
	 * @param staffPermission
	 * @param othersPermission
	 * @param path
	 * @throws FTPCommandException
	 */
	public void chmod(String ownerPermission, String staffPermission,
			String othersPermission, String path) throws FTPCommandException {
		String chmodCommand = new StringBuffer("chmod ")
				.append(ownerPermission).append(staffPermission).append(
						othersPermission).append(" ").append(path).toString();
		boolean permissionAltered = false;
		try {
			permissionAltered = ftp.sendSiteCommand(chmodCommand);
		} catch (IOException ioException) {
			throw new FTPCommandException(
					new StringBuffer()
							.append(
									"Failed to change permission on the directory. Permission Denied. Command: ")
							.append(chmodCommand).toString(), ioException);
		}
		if (!permissionAltered) {
			throw new FTPCommandException(
					new StringBuffer()
							.append(
									"Failed to change permission on the directory. Permission Denied. Command: ")
							.append(chmodCommand).toString());
		}
	}

	/**
	 * Renames the file
	 * 
	 * @param oldName
	 *            - The existing name of the file
	 * @param newName
	 *            - New name of the file
	 * @throws FTPCommandException
	 *             - indicates renaming failed
	 */
	public void mv(String oldName, String newName) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(oldName)) {
			throw new FTPCommandException(
					"OldName has not been specified in the rename method of FTPCommand");
		}
		if (ASCUtil.isNullOrEmpty(newName)) {
			throw new FTPCommandException(
					"NewName has not been specified in the rename method of FTPCommand");
		}
		try {
			if (!ftp.rename(oldName, newName)) {
				if (!ftp.deleteFile(newName)) {
					throw new FTPCommandException(new StringBuffer().append(
							"Couldn't delete file ").append(newName).toString());
				}
				if (!ftp.rename(oldName, newName)) {
					throw new FTPCommandException(new StringBuffer().append(
							"Couldn't rename file from ").append(oldName)
							.append(" to ").append(newName).toString());
				}
			}
		} catch (IOException ioException) {
			throw new FTPCommandException(new StringBuffer().append(
					"Couldn't rename from ").append(oldName).append(" to ")
					.append(newName).toString(), ioException);
		}
	}

	/**
	 * Method that shows the current working directory at the remote location.
	 * 
	 * @return boolean
	 * @throws FTPCommandException
	 */
	public String pwd() throws FTPCommandException {
		try {
			return ftp.printWorkingDirectory();
		} catch (IOException ioExcep) {
			throw new FTPCommandException(
					"Failed to get the working directory using FTP. Received Message : "
							+ ioExcep.getMessage());
		}
	}

	/**
	 * Method that provides the facility of changing the current working
	 * directory at the remote location. The method will return a boolean and
	 * will never throw an exception
	 * 
	 * @param pathname
	 * @return boolean
	 * @throws FTPCommandException
	 */
	public boolean lcd(String pathname) throws FTPCommandException {
		if (ASCUtil.isNullOrEmpty(pathname)) {
			throw new FTPCommandException(new StringBuffer().append(
					"pathname : ").append(pathname).append(
					" of the lcd method of FTPCommand can't be Null.")
					.toString());
		}
		boolean success = false;
		try {
			success = ftp.sendSiteCommand("lcd " + pathname);
		} catch (IOException ioExcep) {
			throw new FTPCommandException(ioExcep);
		}
		return success;
	}

}
